"""
Created on 2019年7月5日
切割长图并按照给定的比例拼接图片,
切割后的图片不进行缩放
同时支持在图片上添加页码
@author: shily
"""
import math
import os
import sys
import time
from builtins import str
from enum import Enum
# 线性代数库
# import numpy.linalg as ln
from sys import argv

import numpy as np
# Pillow
from PIL import Image, ImageDraw, ImageFont

from ImgConfig import COL_COUNT, RATIO, IMG_EXTENSIONS, \
    DRAW_PAGE_NUM, PAGE_NUM_BUTTOM_SPACE, PAGE_NUM_SIDE_SPACE, \
    USE_ABS_DIST_PATH, DIST_PATH, CONTENT_RECOGNITION, PAGE_NUM_POSITION, PageNumPosition, SINGLE_PAGE_NUM

'''
 图片所在目录手动修改无效
'''
image_base_path: str = None
'''
输出图片的绝对路径
'''
image_dist_path: str = None

'''
图片总数
'''
image_total_count = 0


def reconstruct_image(*image_paths):
    """
    重构图片
    保持所有生成的图片高度一致
    :param image_paths:图片路径
    """
    # 当前页码
    page_num = 1
    for i in range(len(image_paths)):
        image_path = image_paths[i]

        print("切割图片%s" % image_path)

        image = Image.open(os.path.join(image_base_path, image_path))
        # 获取图像大小
        (width, height) = image.size
        # 新图像的高度
        y_step = background_image_height = math.floor(width / RATIO)

        # 有页码, 减去页码字体高度, 以及页码距离底端的边距
        if DRAW_PAGE_NUM:
            page_num_font_height = get_page_num_font_height(width * COL_COUNT)
            y_step -= page_num_font_height + PAGE_NUM_BUTTOM_SPACE

        # y轴开始坐标
        y_start_position = 0
        # 当前切割的图片的索引
        i = 0

        # 切割后的小图图以及背景图
        sub_images = []

        # 是否已经到达结尾
        if_final = False

        while y_start_position < height:

            # y方向开始位置和结束位置的偏移量,为了解决切割到文字的问题
            start_offset = -1
            end_offset = 0

            # 切割结束的y坐标    
            y_end_position = y_start_position + y_step

            if y_end_position > height:
                y_end_position = height
                if_final = True

            # 计算偏移量, 以保证不会切割到文字
            if CONTENT_RECOGNITION:
                if y_start_position > 0 and start_offset != 0:
                    start_offset = cal_no_content_line_offset(image, y_start_position, Direction.DOWN,
                                                              background_image_height)
                end_offset = cal_no_content_line_offset(image, y_end_position, Direction.UP, -background_image_height)

            # 极端情况下,偏移过后图像高度为0,按照不偏移处理
            if y_end_position + end_offset <= y_start_position + start_offset:
                start_offset = end_offset = 0

            # 裁剪图像
            crop_img = image.crop((0, y_start_position + start_offset, width, y_end_position + end_offset))
            # 本次的结束位置作为下一次开始位置
            y_start_position = y_end_position + end_offset

            crop_img_arr = np.asarray(crop_img)
            # 如果当前图片所有像素值都相同, 丢弃此图片,视为无内容
            if not (crop_img_arr == crop_img_arr[0][0]).all():
                sub_images.append(crop_img)

            # 已经生成n张图像或者已经到达图像结尾
            if (if_final or len(sub_images) == COL_COUNT) and len(sub_images) > 0:
                # 初始化png图像
                background_img = init_background_img((width * COL_COUNT, background_image_height), 'white')
                for j in range(len(sub_images)):
                    sub_img = sub_images[j]
                    # 粘贴小图到背景
                    background_img.paste(sub_img, (j * width, 0))
                    # 绘制页码
                    if not SINGLE_PAGE_NUM:
                        draw_page_num(background_img, page_num, width, ((j + 1) * width, background_image_height),
                                      'darkcyan')
                        page_num += 1
                # 添加页码
                if SINGLE_PAGE_NUM:
                    draw_page_num(background_img, page_num, width * COL_COUNT,
                                  (width * COL_COUNT, background_image_height),
                                  'darkcyan')
                    page_num += 1
                # 保存图片
                background_img.save(
                    os.path.join(image_dist_path,
                                 os.path.splitext(os.path.basename(image_path))[0] + "_" + str(i) + ".png"))
                i += 1
                # 生成一张图片后重置相关参数
                sub_images = []
        global image_total_count
        image_total_count += i
        print("共切割为%s张" % i)


def reconstruct_image_without_space(*image_paths):
    """
    :param image_paths: 图片路径
    TODO 图片无缝拼接功能待完成
    """

    image_index = 0
    # 页码
    page_num = 1
    # 小图
    sub_images = []
    # 背景图
    background_img = None


def generate_image(images, image_index, start_y, height):
    current_height = 0
    image_count = len(images)
    crop_images = []
    result_image_width = 0
    while current_height < height and image_index < image_count:
        image = images[image_index]
        (img_width, img_height) = image.size
        result_image_width = max(img_width, result_image_width)
        # 如果图片剩余高度小于待获取高度, 截取剩余部分
        start_y += cal_no_content_line_offset(image, start_y)
        end_y = cal_content_line_offset(image, img_height, Direction.UP)

        if img_height - start_y < height - current_height:
            crop_image = image.crop((0, start_y, img_width, end_y))
            image_index += 1
            start_y = 0
        else:
            end_y = start_y + (height - current_height)
            end_y += cal_no_content_line_offset(image, end_y)
            crop_image = image.crop((0, start_y, img_width, end_y))
            start_y = end_y
        current_height += crop_image.size[1]
        crop_images.append(crop_image)

    paste_height = 0
    result_image = Image.new('RGBA', (result_image_width, current_height))
    for sum_image in crop_images:
        result_image.paste(sum_image, (0, paste_height))
        paste_height += sum_image.size[1]
    return result_image


class Direction(Enum):
    """
    偏移量计算方向
    """
    UP = 0
    DOWN = 1


def cal_no_content_line_offset(image, y_position, direction=Direction.DOWN, extreme_value=None):
    """
    计算切割线偏移量(跳过有内容区域, 防止切割到)
    求取图像当前行像素值矩阵的秩, 如果秩为1说明只有一种颜色, 视为背景色
    矩阵求秩效率较低
    :param image: Image 对象
    :param y_position: 起始y坐标
    :param direction: 向上(0)还是向下(1)
    :param extreme_value: 极端值, 当偏移量超出极端值时,返回极端值
    """
    __, height = image.size
    image_arr = np.asarray(image)
    offset = 0
    # 偏移量基础上留出偏移量
    margin = 2
    # 步长
    step = 1

    if direction == Direction.DOWN:
        if not extreme_value:
            extreme_value = height - margin - y_position
        if not y_position or y_position < 0:
            y_position = 0
        while (y_position + offset) < height and offset < extreme_value:
            # if ln.matrix_rank(image_arr[y_position + offset][:][:]) == 1:
            # 当前行所有像素都相同, 说明已经找到没有内容的位置
            if (image_arr[y_position + offset][:][:] == image_arr[y_position + offset][0][:]).all():
                break
            offset += step
        if offset != 0:
            # 增加两个像素的余量
            offset += margin
    else:
        if not extreme_value:
            extreme_value = -height + margin + y_position
        if not y_position or y_position >= height:
            y_position = height - 1
        while (y_position + offset) > 0 and offset > extreme_value:
            if (image_arr[y_position + offset][:][:] == image_arr[y_position + offset][0][:]).all():
                break
            offset -= step
        if offset != 0:
            # 增加两个像素的余量
            offset -= margin
    return offset


def cal_content_line_offset(image, y_position, direction=Direction.DOWN, extreme_value=None):
    """
    计算切割线偏移量(跳过没有内容区域, 防止图片中出现大片的空白区域)
    求取图像当前行像素值矩阵的秩, 如果秩为1说明只有一种颜色, 视为背景色
    矩阵求秩效率较低
    :param image: Image 对象
    :param y_position: 起始y坐标
    :param direction: 向上(0)还是向下(1)
    :param extreme_value: 极端值, 当偏移量超出极端值时,返回极端值
    """
    __, height = image.size
    image_arr = np.asarray(image)
    offset = 0
    # 偏移量基础上留出偏移量
    margin = 2
    # 步长
    step = 1

    if direction == Direction.DOWN:
        if not extreme_value:
            extreme_value = height - margin - y_position
        if not y_position or y_position < 0:
            y_position = 0
        while (y_position + offset) < height and offset < extreme_value:
            # 当前行并非所有像素都相同, 说明已经找到有内容的位置
            if not (image_arr[y_position + offset][:][:] == image_arr[y_position + offset][0][:]).all():
                break
            offset += step
        if offset != 0:
            # 增加两个像素的余量
            offset -= margin
    else:
        if not extreme_value:
            extreme_value = -height + margin + y_position
        if not y_position or y_position >= height:
            y_position = height - 1
        while (y_position + offset) > 0 and offset > extreme_value:
            # 当前行并非所有像素都相同, 说明已经找到有内容的位置
            if not (image_arr[y_position + offset][:][:] == image_arr[y_position + offset][0][:]).all():
                break
            offset -= step
        if offset != 0:
            # 增加两个像素的余量
            offset += margin
    return offset


def get_extension(file_path):
    """
    获取文件扩展名
    """
    return os.path.splitext(file_path)[1].lower()


def draw_page_num(image, page_num, sub_img_width, position=(), font_color=(0, 0, 0)):
    """
    在图片上绘制页码
    :param image: Image对象
    :param page_num: 页码, 如果x, y都不制定,默认在图像右下角
    :param sub_img_width: 每页宽度
    :param position: 页码所在页右下角位置
    :param font_color: 字体颜色, 默认黑色
    默认使用全局的页码边距
    """
    if DRAW_PAGE_NUM:
        page_num_str = str(page_num)
        position += image.size

        (x, y) = position[:2]

        font = generate_page_num_font(image.size[0])
        (textWidth, textHeight) = font.getsize(page_num_str)

        draw = ImageDraw.Draw(image)

        if PAGE_NUM_POSITION == PageNumPosition.LEFT:
            draw_position = (x - sub_img_width + PAGE_NUM_SIDE_SPACE, y - textHeight - PAGE_NUM_BUTTOM_SPACE)
        elif PAGE_NUM_POSITION == PageNumPosition.MIDDLE:
            draw_position = (x - (sub_img_width + textWidth) / 2, y - textHeight - PAGE_NUM_BUTTOM_SPACE)
        else:
            draw_position = (x - textWidth - PAGE_NUM_SIDE_SPACE, y - textHeight - PAGE_NUM_BUTTOM_SPACE)
        draw.text(draw_position, page_num_str, font_color, font)
    return image


def resource_path(relative_path):
    """
    生成资源文件目录访问路径
    用于获取pyinstaller打包后的资源文件路径
    :param relative_path: 相对路径
    """
    base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, relative_path)


def generate_page_num_font(image_width):
    """
    生成图片使用的页码字体
    """
    font_size = math.floor(image_width / 30)
    font = ImageFont.truetype(resource_path(r'res\SourceCodePro-Regular.ttf'), font_size)
    return font


def get_page_num_font_height(image_width):
    """
    获取页码高度
    """
    return generate_page_num_font(image_width).getsize('0')[1]


def init_background_img(size, bg_color):
    """
    初始化背景图片
    :param size: 图片大小
    :param bg_color: 背景图片颜色
    """

    result_image = Image.new('RGBA', size, bg_color)

    return result_image


def start(images_path=None):
    """
    开始切分图片
    :param images_path: 待处理图片路径
    """
    start_time = time.time()
    images_path = init_variable(images_path)
    print_configuration()
    # 判断是图像文件还是目录
    if os.path.isdir(images_path):
        image_files = []
        # 遍历处理目录下所有图片
        files = os.listdir(images_path)
        for path in files:
            if os.path.isfile(os.path.join(image_base_path, path)) and get_extension(path) in IMG_EXTENSIONS:
                image_files.append(os.path.join(image_base_path, path))
        reconstruct_image(*image_files)
    else:
        if get_extension(images_path) in IMG_EXTENSIONS:
            reconstruct_image(images_path)
    print('共生成%s张图片' % image_total_count)
    print('耗时 %fs' % (time.time() - start_time))
    print('------------Finish----------')


def print_configuration():
    """
    打印配置信息
    """
    print('---------------START------------')
    print('图片输出路径 %s' % image_dist_path)
    print('图片栏位数 %s' % COL_COUNT)
    print('是否识别内容?  %s' % ('是' if CONTENT_RECOGNITION else '否'))
    print('宽高比 %s' % RATIO)
    print('添加页码? %s' % ('是' if DRAW_PAGE_NUM else '否'))
    print('每张图片只使用一个页码? %s' % ('是' if SINGLE_PAGE_NUM else '否'))
    print('页码位置? %s' % PAGE_NUM_POSITION)
    print('页码距离图像边缘距离 底边距%s 侧边距%s' % (PAGE_NUM_BUTTOM_SPACE, PAGE_NUM_SIDE_SPACE))


def init_variable(images_path):
    """
    初始化基本变量
    返回要处理的文件或目录
    :param images_path: 要处理的文件或目录
    """
    global image_base_path, image_dist_path
    if not images_path:
        # 取命令行参数作为待处理路径 
        if len(argv) > 1:
            images_path = argv[1]
        else:
            # 否则使用当前脚本所在目录作为basePath
            images_path = os.path.dirname(os.path.abspath(__file__))

    if os.path.isdir(images_path):
        image_base_path = images_path
    else:
        image_base_path = os.path.dirname(images_path)

    if USE_ABS_DIST_PATH:
        image_dist_path = DIST_PATH
    else:
        image_dist_path = os.path.join(image_base_path, DIST_PATH)
    # 创建目标文件夹
    if not os.path.exists(image_dist_path):
        os.mkdir(image_dist_path)
    return images_path


if __name__ == '__main__':
    start()
os.system("pause")
